package com.dd.keel.core.common.utils;

/**
 * HTTP客户端的操作类。
 * 默认连接超时是5秒，读数据超时是3秒。
 * @author Kevin.XU
 */

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Properties;

import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpClientConnection;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.util.EntityUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HttpClient {
	
	final static private Logger LOGGER = LoggerFactory.getLogger(HttpClient.class);
	
	final public static String METHOD_POST = "POST";
	final public static String METHOD_GET  = "GET";
	
	final public static String CHARSET_UTF8 = "UTF-8";
	final public static String CHARSET_GBK  = "GBK";
	
	final public static int HTTP_VERSION_1_0 = 0;
	final public static int HTTP_VERSION_1_1 = 1;
	
	private HttpParams params = null;
	private HttpContext context = null;
	private HttpHost host = null;
	private BasicHttpProcessor httpproc = null;
	private HttpRequestExecutor httpexecutor = null;
	private DefaultHttpClientConnection conn = null;
	private ConnectionReuseStrategy connStrategy = null;	
	
	private String charset = null;
	
	private String defaultUri = null;
	
	private int connectTimeoutSeconds = 5;
	private int soTimeoutSeconds      = 3;
	
	public HttpClient(String serverIP,int serverPort)
	{
		this(serverIP,serverPort,CHARSET_UTF8);
	}
	
	public HttpClient(String serverIP,int serverPort,String charset)
	{
		this(serverIP,serverPort,charset,-1,-1);
	}
	
	public HttpClient(String serverIP,int serverPort,String charset,int connectTimeoutSeconds,int soTimeoutSeconds)
	{
	    this(serverIP,serverPort,HTTP_VERSION_1_1,charset,-1,-1);
	}
	
    public HttpClient(String serverIP,int serverPort,int httpVersion,String charset,int connectTimeoutSeconds,int soTimeoutSeconds)
    {
        if(connectTimeoutSeconds>0) this.connectTimeoutSeconds = connectTimeoutSeconds;
        if(soTimeoutSeconds>0) this.soTimeoutSeconds = soTimeoutSeconds;
        this.charset = charset;
        params = new BasicHttpParams();
        if(httpVersion==HTTP_VERSION_1_0)
        {
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_0);
        }
        else
        {
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        }
        
        //HttpProtocolParams.setContentCharset(params, "UTF-8");
        //HttpProtocolParams.setUserAgent(params, "HttpComponents/1.1");
        //HttpProtocolParams.setUseExpectContinue(params, true);
        
        httpproc = new BasicHttpProcessor();
        
        /****more interceptors should cost much cpu time***/
        /****HttpClient based on HttpCore is more slower than HttpCore , because it add almost all interceptors *****/
        // Required protocol interceptors
        httpproc.addInterceptor(new RequestContent());
        //httpproc.addInterceptor(new RequestTargetHost());
        
        // Recommended protocol interceptors
        //httpproc.addInterceptor(new RequestConnControl());
        //httpproc.addInterceptor(new RequestUserAgent());
        //httpproc.addInterceptor(new RequestExpectContinue());

        httpexecutor = new HttpRequestExecutor();
        
        context = new BasicHttpContext();
        host = new HttpHost(serverIP, serverPort);
        
        conn = new DefaultHttpClientConnection();
        connStrategy = new DefaultConnectionReuseStrategy();
        
        context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
        context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, host);  
    }	
	
	public HttpClient(String url)
	{
		this(url,CHARSET_UTF8);
	}
	
	public HttpClient(String url,String charset)
	{
		this(url,charset,-1,-1);
	}
	
	public HttpClient(String url,String charset,int connectTimeoutSeconds,int soTimeoutSeconds)
	{
	    this(url,charset,-1,-1,HTTP_VERSION_1_1);
	}
	
    public HttpClient(String url,String charset,int connectTimeoutSeconds,int soTimeoutSeconds,int httpVersion)
    {
        if(connectTimeoutSeconds>0) this.connectTimeoutSeconds = connectTimeoutSeconds;
        if(soTimeoutSeconds>0) this.soTimeoutSeconds = soTimeoutSeconds;
        this.charset = charset;
        params = new BasicHttpParams();
        if(httpVersion==HTTP_VERSION_1_0)
        {
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_0);
        }
        else
        {
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        }
        //HttpProtocolParams.setContentCharset(params, "UTF-8");
        //HttpProtocolParams.setUserAgent(params, "HttpComponents/1.1");
        //HttpProtocolParams.setUseExpectContinue(params, true);
        
        httpproc = new BasicHttpProcessor();
        
        /****more interceptors should cost much cpu time***/
        /****HttpClient based on HttpCore is more slower than HttpCore , because it add almost all interceptors *****/
        // Required protocol interceptors
        httpproc.addInterceptor(new RequestContent());
        //httpproc.addInterceptor(new RequestTargetHost());
        
        // Recommended protocol interceptors
        //httpproc.addInterceptor(new RequestConnControl());
        //httpproc.addInterceptor(new RequestUserAgent());
        //httpproc.addInterceptor(new RequestExpectContinue());
        
        httpexecutor = new HttpRequestExecutor();
        
        context = new BasicHttpContext();
        String[] tempArray = StringUtil.parseUrl(url);
        if(tempArray==null)
        {
            throw new RuntimeException("url {"+url+"} is wrong!");
        }
        host = new HttpHost(tempArray[0], Integer.parseInt(tempArray[1]));
        defaultUri = tempArray[2];
        
        conn = new DefaultHttpClientConnection();
        connStrategy = new DefaultConnectionReuseStrategy();
        
        context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
        context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, host);  
    }
	
	public void connect() throws Exception
	{
		Socket socket = new Socket();
		socket.connect(new InetSocketAddress(host.getHostName(),host.getPort()), this.connectTimeoutSeconds*1000);
		socket.setSoTimeout(this.soTimeoutSeconds*1000);
		conn.bind(socket, params);
	}
	
	public void disconnect()
	{
		if(conn!=null && conn.isOpen())
		{
			try {
				conn.close();
			} catch (IOException ex) {
				LOGGER.error( ex.getMessage(), ex );
			}
		}
	}
	
	/**
	 * Accepptable fields: 
	 * Content-Type 
	 * Connection : keep-alive
	 * User-Agent 
	 * @param request
	 * @param headerProps
	 */
	private void addHeaders(BasicHttpEntityEnclosingRequest request, Properties headerProps)
	{
		if(headerProps==null || headerProps.isEmpty()) return;
		Enumeration keyEnums = headerProps.keys();
		while(keyEnums.hasMoreElements())
		{
			String key = (String)keyEnums.nextElement();
			String value = headerProps.getProperty(key);
			if(value!=null)
			{
				value = value.trim();
				if(!value.equals(""))
				{
					request.addHeader( key, value );
				}
			}
		}
		//if(!request.containsHeader("Content-Type"))
		//{
			request.addHeader("Content-Type", "application/x-www-form-urlencoded" );
		//}
		request.addHeader("Host", host.getHostName()+":"+host.getPort() );
		request.addHeader("Accept-Charset", this.charset );
	}
	
	public TextResponseBean request(String method, String requestContent, Properties props) throws Exception
	{
		if(this.defaultUri==null) throw new Exception("URI not be set!");
		return request(defaultUri,method,requestContent,props);
	}
	
	public TextResponseBean request(String uri, String method, String requestContent, Properties props) throws Exception
	{
		if(props==null) props = new Properties();
		TextResponseBean responseBean = new TextResponseBean();
        try {
        	
			BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(method, uri);
			
			//add header fields
			addHeaders(request,props);
			
			ByteArrayEntity entity2 = null;
			if(METHOD_POST.equals(method))
			{
				entity2 = new ByteArrayEntity(requestContent.getBytes(this.charset));
				request.setEntity(entity2);
			}
			else if(METHOD_GET.equals(method))
			{
				entity2 = new ByteArrayEntity(new byte[]{});
				request.setEntity(entity2);
			}
			
			LOGGER.trace( "{} request to {}", method, request.getRequestLine().getUri() );
			LOGGER.trace("REQ-BODY={}",requestContent);
			
			request.setParams(params);

			httpexecutor.preProcess(request, httpproc, context);
			HttpResponse response = httpexecutor.execute(request, conn, context);
			response.setParams(params);
			httpexecutor.postProcess(response, httpproc, context);
			
			LOGGER.trace( "{} receive response, status is {}", method, response.getStatusLine() );
			
			HttpEntity entity = response.getEntity();
			
			responseBean.setBodyContent( EntityUtils.toString(entity) );
			
			entity.consumeContent();
			
			responseBean.setStatusCode(response.getStatusLine().getStatusCode());
			
			LOGGER.trace("RESP-BODY={}",responseBean.getBodyContent());
			
			if (!connStrategy.keepAlive(response, context)) {
				LOGGER.trace( "Connection close..." );
				conn.close();
			} else {
				LOGGER.trace( "Connection kept alive..." );
			}
        } catch (UnknownHostException ex) {
        	LOGGER.error( ex.getMessage(), ex );
			throw ex;
		} catch (IOException ex) {
			LOGGER.error( ex.getMessage(), ex );
			throw ex;
		} catch (HttpException ex) {
			LOGGER.error( ex.getMessage(), ex );
			throw ex;
		} catch (Exception ex) {
			LOGGER.error( ex.getMessage(), ex );
			throw ex;
		}
		return responseBean;
	}
	
	public BinaryResponseBean request(String method, byte[] requestContent, Properties props) throws Exception
	{
		if(this.defaultUri==null) throw new Exception("URI not be set!");
		return request(defaultUri,method,requestContent,props);
	}
	
	public BinaryResponseBean request(String uri, String method, byte[] requestContent, Properties props) throws Exception
	{
		if(props==null) props = new Properties();
		BinaryResponseBean responseBean = new BinaryResponseBean();
        try {
        	
			BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(method, uri);
			
			//add header fields
			addHeaders(request,props);
			
			ByteArrayEntity entity2 = null;
			if(METHOD_POST.equals(method))
			{
				entity2 = new ByteArrayEntity(requestContent);

				request.setEntity(entity2);
			}
			else if(METHOD_GET.equals(method))
			{
				entity2 = new ByteArrayEntity(new byte[]{});
				request.setEntity(entity2);
			}
			
			LOGGER.trace( "{} request to {}", method, request.getRequestLine().getUri() );
			if(LOGGER.isTraceEnabled())
			{
				String dataText = null;
				if(requestContent!=null)
				{
					dataText = ByteUtil.byte2hexWithBlank(requestContent);
				}
				else
				{
					dataText = "";
				}
				LOGGER.trace("REQ-BODY={}",dataText);
			}
			
			request.setParams(params);
			httpexecutor.preProcess(request, httpproc, context);
			HttpResponse response = httpexecutor.execute(request, conn, context);
			response.setParams(params);
			httpexecutor.postProcess(response, httpproc, context);
			
			LOGGER.trace( "{} receive response, status is {}", method, response.getStatusLine() );
			
			HttpEntity entity = response.getEntity();
			
			responseBean.setBodyContent(EntityUtils.toByteArray(entity));
			
			entity.consumeContent();
			
			responseBean.setStatusCode(response.getStatusLine().getStatusCode());
			
			if(LOGGER.isTraceEnabled())
			{
				String dataText = null;
				if(responseBean.getBodyContent()!=null)
				{
					dataText = ByteUtil.byte2hexWithBlank(responseBean.getBodyContent());
				}
				else
				{
					dataText = "";
				}
				LOGGER.trace("RESP-BODY={}",dataText);
			}
			
			if (!connStrategy.keepAlive(response, context)) {
				LOGGER.trace( "Connection close..." );
				conn.close();
			} else {
				LOGGER.trace( "Connection kept alive..." );
			}
        } catch (UnknownHostException ex) {
        	LOGGER.error( ex.getMessage(), ex );
			throw ex;
		} catch (IOException ex) {
			LOGGER.error( ex.getMessage(), ex );
			throw ex;
		} catch (HttpException ex) {
			LOGGER.error( ex.getMessage(), ex );
			throw ex;
		} catch (Exception ex) {
			LOGGER.error( ex.getMessage(), ex );
			throw ex;
		}
		return responseBean;
	}
	
	public String toString()
	{
		return "HttpClient{"+host.getHostName()+","+host.getPort()+"}";
	}
	
	public class BinaryResponseBean
	{
		private int statusCode = 200;
		private byte[] bodyContent = null;
		public int getStatusCode() {
			return statusCode;
		}
		public void setStatusCode(int statusCode) {
			this.statusCode = statusCode;
		}
		public byte[] getBodyContent() {
			return bodyContent;
		}
		public void setBodyContent(byte[] bodyContent) {
			this.bodyContent = bodyContent;
		}
	}
	
	public class TextResponseBean
	{
		private int statusCode = 200;
		private String bodyContent = null;
		public int getStatusCode() {
			return statusCode;
		}
		public void setStatusCode(int statusCode) {
			this.statusCode = statusCode;
		}
		public String getBodyContent() {
			return bodyContent;
		}
		public void setBodyContent(String bodyContent) {
			this.bodyContent = bodyContent;
		}
	}
	
}
